package com.niodata.dp.core.dblock;

import com.niodata.dp.log.LogUtil;
import com.niodata.dp.util.JsonUtil;
import java.io.IOException;
import java.util.Date;
import java.util.UUID;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("mysqlLock")
@Transactional
public class DefaultMysqlLock implements DbLock {

  private static final Logger logger = Logger.getLogger(DefaultMysqlLock.class);
  private final ThreadLocal<Lock> lockMap = new ThreadLocal<Lock>();
  private final long lockMaxTime = 10 * 1000;//10 seconds
  @Autowired
  private MysqlLocker locker;

  @Override
  public void tryLock(String lockName, int timeout) {
    Lock lock = lockMap.get();
    if (lock != null) {
      return;
    }
    String owner = this.buildLockOwner();
    long timeLeft = timeout * 1000;
    while (timeLeft > 0) {
      try {
        tryLockInner(lockName, owner);
        break;
      } catch (Exception e) {
        //check if timeout lock exists.
        Lock lock1 = this.locker.getLock(lockName);
        if (lock1 != null) {
          long lockTime = System.currentTimeMillis() - lock1.getTime().getTime();
          if (lockTime > this.lockMaxTime) {
            //delete time out lock
            locker.releaseLock(lockName, lock1.getOwner());
            try {
              logger.warn(String.format("time out lock detected: %s", JsonUtil.toJson(lock1)));
            } catch (IOException ioe) {
              LogUtil.logStackTrace(logger, e);
            }
          }
        }
        try {
          Thread.sleep(50);
          timeLeft -= 50;
        } catch (InterruptedException e1) {
          e1.printStackTrace();
        }
      }
    }
    lock = this.locker.getLock(lockName);
    if (lock == null || !lock.getOwner().equals(owner)) {
      throw new RuntimeException("get lock failed");
    } else {
      lockMap.set(lock);
    }
  }

  @Override
  public void tryLock(String lockName) {
    Lock lock = lockMap.get();
    if (lock != null) {
      return;
    }

    String owner = this.buildLockOwner();
    Lock lock1 = this.locker.getLock(lockName);
    if (lock1 != null) {
      long lockTime = System.currentTimeMillis() - lock1.getTime().getTime();
      if (lockTime > this.lockMaxTime) {
        //delete time out lock
        locker.releaseLock(lockName, lock1.getOwner());
        try {
          logger.warn(String.format("time out lock detected: %s", JsonUtil.toJson(lock1)));
        } catch (IOException e) {
          LogUtil.logStackTrace(logger, e);
        }
      }
    }

    while (true) {
      try {
        tryLockInner(lockName, owner);
        break;
      } catch (Exception e) {
        try {
          Thread.sleep(200);
        } catch (InterruptedException e1) {
          e1.printStackTrace();
        }
      }
    }
    lock = this.locker.getLock(lockName);
    if (lock == null || !lock.getOwner().equals(owner)) {
      throw new RuntimeException("get lock failed");
    } else {
      lockMap.set(lock);
    }
  }

  @Override
  public void releaseLock(String lockName) {
    this.locker.releaseLock(lockName, this.lockMap.get().getOwner());
    this.lockMap.remove();
  }

  @Override
  public Lock getLock(String lockName) {
    return this.locker.getLock(lockName);
  }

  private void tryLockInner(String lockName, String owner) throws Exception {
    this.locker.lock(lockName, new Date(), owner);
  }

  private String buildLockOwner() {
    return UUID.randomUUID().toString();
  }


}
